using System;
using System.Xml;
using System.Xml.XPath;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
using System.IO;

using Dida.Tool;

namespace Dida.Test
{
	public class TestProject
	{
		HashSet<Assembly> assemblies = new HashSet<Assembly>();
		DOMCollection<UnitTest> tests = new DOMCollection<UnitTest>();

		HashSet<string> usedNames = new HashSet<string>();

		public TestProject(string srcFileName)
		{
			string curPath = Path.GetFullPath("." + Path.DirectorySeparatorChar +
				Path.GetDirectoryName(srcFileName));
						
			XmlReaderSettings readerSettings = new XmlReaderSettings();
			readerSettings.IgnoreComments = true;
			readerSettings.IgnoreWhitespace = true;
			XmlReader reader = XmlReader.Create(srcFileName, readerSettings);

			reader.MoveToContent();
			if (reader.Name != "TestProject")
			{
				throw new XmlReaderException("\"TestProject\" element expected", reader);
			}

			List<string> inclusionStack = new List<string>();
			inclusionStack.Add(FileUtils.NormalizeFileName(srcFileName,curPath));
			ReadRecursive(reader, inclusionStack, curPath);
		}

		private void ReadRecursive(XmlReader reader, List<string> inclusionStack,string curPath)
		{
			if (reader.IsEmptyElement)
			{
				reader.ReadStartElement();
				return;
			}
			else
			{
				reader.ReadStartElement();
				while (reader.NodeType != XmlNodeType.EndElement)
				{
					switch (reader.Name)
					{
					case "Assemblies":
						LoadAssemblies(reader);
						break;

					case "Tests":
						LoadTests(reader);
						break;

					case "Include":
						LoadInclude(reader, inclusionStack, curPath);
						break;

					default:
						reader.Skip();
						break;
					}
				}
				reader.ReadEndElement();
			}
		}

		private void LoadInclude(XmlReader reader,List<string> inclusionStack,string curPath)
		{
			string src = reader.GetAttribute("src");
			if (src == null)
			{
				throw new XmlAttributeNotSpecifiedException(reader, "src");
			}

			if (inclusionStack.Contains(FileUtils.NormalizeFileName(src,curPath)))
			{
				throw new XmlAttributeException("The file \"" + src + 
					"\" directly or indirectly includes itself.", reader, "src");
			}

			reader.Skip();	// skip any content of the Include element

			FileUtils.NormalizeFlags normalizeFlags = FileUtils.NormalizeFlags.KeepCase;
			//if (Environment.OSVersion.Platform != PlatformID.Unix)
			//	normalizeFlags |= FileUtils.NormalizeFlags.WantDriveLetter;
			string absoluteSrc = FileUtils.NormalizeFileName(
				src, curPath, normalizeFlags);
						
			XmlReaderSettings readerSettings = new XmlReaderSettings();
			readerSettings.IgnoreComments = true;
			readerSettings.IgnoreWhitespace = true;
			XmlReader includedReader = XmlReader.Create(absoluteSrc, readerSettings);

			includedReader.MoveToContent();
			if (includedReader.Name != "TestProject")
			{
				throw new XmlReaderException("\"TestProject\" element expected", reader);
			}
			
			inclusionStack.Add(src);
			ReadRecursive(includedReader, inclusionStack, Path.GetDirectoryName(absoluteSrc));
			inclusionStack.RemoveAt(inclusionStack.Count - 1);
		}

		private void LoadAssemblies(XmlReader reader)
		{
			if (reader.IsEmptyElement)
			{
				reader.ReadStartElement();
				return;
			}

			reader.ReadStartElement();
			while (reader.NodeType != XmlNodeType.EndElement)
			{
				if(reader.NodeType == XmlNodeType.Element && 
					reader.Name == "Assembly")
				{
					string src = reader.GetAttribute("src");
					if (src == null)
						throw new XmlAttributeNotSpecifiedException(reader, "src");

					try
					{
						Assembly asm = Assembly.LoadFrom(src);
						assemblies.Add(asm);
					}
					catch (Exception err)
					{
						throw new XmlAttributeException(err, reader, "src");
					}

					if (reader.IsEmptyElement)
						reader.ReadStartElement();
					else
					{
						reader.ReadStartElement();
						reader.ReadEndElement();
					}
				}
				else
					reader.Skip();
			}
			reader.ReadEndElement();
		}

		private void LoadTests(XmlReader reader)
		{
			if (reader.IsEmptyElement)
			{
				reader.ReadStartElement();
				return;
			}
			else
			{
				reader.ReadStartElement();
				while (reader.NodeType != XmlNodeType.EndElement)
				{
					switch (reader.Name)
					{
					case "ParametricTest":
						throw new XmlReaderException("The \"ParametricTest\" element is " + 
							"deprecated. Use \"Test\" instead.", reader);

					case "Test":
						{
							UnitTest test = new UnitTest(this, reader);
							tests.Add(test);
						}
						break;

					default:
						reader.Skip();
						break;
					}
				}
				reader.ReadEndElement();
			}
		}

		public Type ResolveClass(string className)
		{
			foreach (Assembly asm in assemblies)
			{
				Type ret = asm.GetType(className);
				if (ret != null)
					return ret;
			}

			throw new Exception("There's no type called \"" + className + 
				"\" in any of the loaded test assemblies.");
		}

		public void RunAll(TestResultListenerWrapper resultListener,UnitTestingSettings settings)
		{
			Assert.AddUser();

			try
			{
				int nFailed = 0;

				ProgressSegment mainSegment = new ProgressSegment();
				
				resultListener.Begin(mainSegment);

				// guesstimate the cost of each task, to get a smooth progress bar
				float[] costs = new float[tests.Count];
				for (int i = 0; i < tests.Count; i++)
					costs[i] = tests[i].Cost;
				ProgressSegment[] testProgress = mainSegment.Split(costs);

				for (int i = 0; i < tests.Count; i++)
				{
					resultListener.BeginTest(tests[i], testProgress[i]);
					tests[i].Run(resultListener, ref nFailed, settings, testProgress[i]);
					resultListener.EndTest();
				}
			}
			catch (TooManyFailuresException)
			{
			}
			finally
			{
				Assert.RemoveUser();
			}

			resultListener.End();
		}

		public void RunAll(ITestResultListener resultListener, UnitTestingSettings settings)
		{
			RunAll(new TestResultListenerWrapper(resultListener), settings);
		}

		public string MakeUniqueName(string baseName)
		{
			string curName;
			int index = 1;
			do
			{
				curName = baseName + " " + index.ToString();
				index++;
			}
			while (usedNames.Contains(curName));
			return curName;
		}

		public bool TryRegisterName(string name)
		{
			if (!usedNames.Contains(name))
			{
				usedNames.Add(name);
				return true;
			}
			else
				return false;
		}

		public DOMCollection<UnitTest> Tests
		{
			get { return tests; }
		}
	}

	internal class TooManyFailuresException : Exception
	{
	}
}
